// main.cpp

#include "Sculpture.h"

#include <fstream>
#include <iostream>
#include <string>
#include <stdlib.h>
#include <getopt.h>
#include "st.h"
#include "stglut.h"

using namespace std;
//
// Forward declaration of application functions.
//
void SetupLights();
void KeyboardCallback(unsigned char key, int xb, int yb);

//
// Globals used by this application.
// As a rule, globals are Evil, but this is a small application
// and the design of GLUT makes it hard to avoid them.
//

// Target frame rate and timer.
static const int kFramesPerSecond = 60;
static STTimer gClockTimer;

// Window size so we can compute aspect ratio.
static int gWindowSizeX = 0;
static int gWindowSizeY = 0;

// A sculpture to draw and animate.
static Sculpture gSculpture;

// Stored mouse position for camera rotation, panning, and zoom.
int gPreviousMouseX = -1;
int gPreviousMouseY = -1;
int gMouseButton = -1;

// Current wireframe setting.
bool gWireframe = false;

int window1, window2;

struct TourSite
{
    vector<float>site;
    int index;
    char status;
};

vector<TourSite> tour_array;
/**
 * 'TimerTick' is the event handler for GLUT timer events
 */
void TimerTickCallback(int value)
{
    //
    // get the amount of wall clock time passed since the timer was
    // started (it was started in the setup() function)
    //
    //float currentTimeInSeconds = gClockTimer.GetElapsedMillis() / 1000.0f;

    //
    // Update the sculpture animation
    //
    //gSculpture.Update(currentTimeInSeconds);
    gSculpture.Update(gClockTimer.GetElapsedMillis()/1000.);

    glutSetWindow(window1);
    glutPostRedisplay();
    glutSetWindow(window2);
    glutPostRedisplay();
    glutSetWindow(window1);

    //
    // schedule new timer event
    //
    glutTimerFunc( (int)(1000.0f / kFramesPerSecond), TimerTickCallback, 0);
}
/**
 * This method is invoked on application start.  Construct
 * any assets to be rendered here.
 */
void Setup()
{
    //
    // Set up the sculpture.
    //
    gSculpture.Setup();

    //
    // Enable OpenGL lighting.
    //
    // SetupLights();

    //
    // start a timer to track how much wall clock time has passed
    //
    gClockTimer.Reset();

    //
    // schedule first GLUT timer event
    //
    glutTimerFunc( (int)(1000.0f / kFramesPerSecond), TimerTickCallback, 0);
    
}

/**
 * Basic OpenGL lighting to help show normal variation of meshes
 */
void SetupLights()
{
    // white light at the eye
    GLfloat light0_position[] = { 0, 0, 1, 0 };
    GLfloat light0_diffuse[] = { 1, 1, 1, 1 };
    GLfloat light0_specular[] = { 1, 1, 1, 1 };
    glLightfv( GL_LIGHT0, GL_POSITION, light0_position );
    glLightfv( GL_LIGHT0, GL_DIFFUSE, light0_diffuse );
    glLightfv( GL_LIGHT0, GL_SPECULAR, light0_specular );

    //red light
    GLfloat light1_position[] = { -.1f, .1f, 0, 0 };
    GLfloat light1_diffuse[] = { .6f, .05f, .05f, 1 };
    GLfloat light1_specular[] = { .6f, .05f, .05f, 1 };
    glLightfv( GL_LIGHT1, GL_POSITION, light1_position );
    glLightfv( GL_LIGHT1, GL_DIFFUSE, light1_diffuse );
    glLightfv( GL_LIGHT1, GL_SPECULAR, light1_specular );

    //blue light
    GLfloat light2_position[] = { .1f, .1f, 0, 0 };
    GLfloat light2_diffuse[] = { .05f, .05f, .6f, 1 };
    GLfloat light2_specular[] = { .05f, .05f, .6f, 1 };
    glLightfv( GL_LIGHT2, GL_POSITION, light2_position );
    glLightfv( GL_LIGHT2, GL_DIFFUSE, light2_diffuse );
    glLightfv( GL_LIGHT2, GL_SPECULAR, light2_specular );

    //material
    GLfloat mat_ambient[] = { 0, 0, 0, 1 };
    GLfloat mat_specular[] = { .8f, .8f, .8f, 1 };
    GLfloat mat_diffuse[] = { .4f, .4f, .4f, 1 };
    GLfloat mat_shininess[] = { 128 };
    glMaterialfv( GL_FRONT, GL_AMBIENT, mat_ambient );
    glMaterialfv( GL_FRONT, GL_SPECULAR, mat_specular );
    glMaterialfv( GL_FRONT, GL_DIFFUSE, mat_diffuse );
    glMaterialfv( GL_FRONT, GL_SHININESS, mat_shininess );

    GLfloat bmat_ambient[] = { 0, 0, 0, 1 };
    GLfloat bmat_specular[] = { 0, .8f, .8f, 1 };
    GLfloat bmat_diffuse[] = { 0, .4f, .4f, 1 };
    GLfloat bmat_shininess[] = { 128 };
    glMaterialfv( GL_BACK, GL_AMBIENT, bmat_ambient );
    glMaterialfv( GL_BACK, GL_SPECULAR, bmat_specular );
    glMaterialfv( GL_BACK, GL_DIFFUSE, bmat_diffuse );
    glMaterialfv( GL_BACK, GL_SHININESS, bmat_shininess );

    GLfloat lmodel_ambient[] = { 0, 0, 0, 1 };
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, 1 );

    glEnable(GL_NORMALIZE);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_LIGHT1);
    glEnable(GL_LIGHT2);
}

void DisplayCallback()
{
    glClearColor(.1f, .1f, .1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glViewport(0, 0, gWindowSizeX, gWindowSizeY);
    
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();

    float aspectRatio = float(gWindowSizeX) / float(gWindowSizeY);
    gluPerspective(60.0f, aspectRatio, 1.f, 50000.f);
    //glFrustum(-10000., 10000., -10000., 10000., 1, );    

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    //
    // Draw the sculpture.
    //
    gSculpture.Draw();

    glutSwapBuffers();
}

void SecondaryDisplayCallback()
{
    glClearColor(.1f, .1f, .1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glViewport(0, 0, gWindowSizeX, gWindowSizeY);

    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();

    float aspectRatio = float(gWindowSizeX) / float(gWindowSizeY);
    gluPerspective(60.0f, aspectRatio, 1.f, 60000.f);
    //glFrustum(-10000., 10000., -10000., 10000., 1, );

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    gSculpture.DrawSecondary();
    glutSwapBuffers();
}

void runCommand(string cmd)
{
        FILE *fp;
    int status;
    char path[255];

    fp = popen(cmd.c_str(), "r");
    if (fp == NULL)
        /* Handle error */;

    while (fgets(path, PATH_MAX, fp) != NULL)
        printf("%s", path);

    status = pclose(fp);
    if (status == -1) {
        /* Error reported by pclose() */
        fprintf(stderr,"Error reported");
    } else {
        /* Use macros described under wait() to inspect `status' in order
           to determine success/failure of command executed by popen() */
        fprintf(stderr, "some good status? %d\n", status);
    }
}

void callMATLAB()
{
    fprintf(stderr, "Calculating new tour path. Please wait\n");
    string cmd = "cd ../../ym/code_local2; matlab < test.m > test.out";
    runCommand(cmd);
}

void writeFile(string filename)
{
    ofstream stream(filename.c_str());
    if (!stream){
        printf("Can't open file.\n");
        exit(2);
    }
    
    string sequence_out = "../../john/Sculpture/demo_sequence.out";
    string cmd = "cp ";
    cmd = cmd +  gSculpture.GetCFile() + " " + sequence_out;
    runCommand(cmd);
    stream << sequence_out <<endl;
    stream << "../../john/Sculpture/demo_sequence.in" <<endl;
    for (int a = 0; a < tour_array.size(); a++)
    {
        struct TourSite ts = tour_array[a];
        vector<float> site = ts.site;
        stream << site[0] << " " << site[1] << " " << site[2] << " " << ts.status << " " << ts.index;
        stream<<endl;
    }
    vector<float> pos = gSculpture.GetCameraPosition();
    stream << pos[0] << " " << pos[1] << " " << pos[2] << " c -1"<<endl;
    stream << gSculpture.GetControlPointOffset() <<endl;
    stream.close();
    
    //also write the new sites file for Sculpture to use
    ofstream stream2("demo_sites_2.txt");
    if (!stream2){
        printf("Can't open file.\n");
        exit(2);
    }
    
    stream2 << pos[0] << " " << pos[1] << " " << pos[2] <<endl;
    for (int a = 0; a < tour_array.size(); a++)
    {
        struct TourSite ts  = tour_array[a];
        if (ts.status == 'u' || ts.status == 'a')
        {
            vector<float>site = ts.site;
            stream2 << site[0] << " " << site[1] << " " << site[2] <<endl;
        }
    }
    stream2.close();

}

void getInputHelper()
{
    writeFile("demo_sites.txt"); 
    callMATLAB();
    gSculpture.SetSFile("demo_sites_2.txt");
    gSculpture.SetCFile("demo_sequence.in");
    gClockTimer.Reset();
    gSculpture.TourSetup();
    fprintf(stderr, "main D_FORWARD: %d\n", D_FORWARD);
    gSculpture.SetState(D_FORWARD);
    printf("Finished recalculation of the tour. Press 'r' to resume tour\n");
    char in;
    cin>>in;
    glutKeyboardFunc(KeyboardCallback);
}

void handleAddSite()
{
    for (int a = 0; a < tour_array.size(); a++)
    {
        TourSite ts = tour_array[a];
        if (ts.status == 'u' || ts.status == 'a')
        {
            vector<float> site = ts.site;
            printf("%d <AVAILABLE SLOT>\n", a);
            printf("%f %f %f\n",site[0], site[1], site[2]);
        }
    }
    printf("%d <AVAILABLE SLOT>\n",tour_array.size());
    printf("Where would you like to add the new site?\n");
        int choice;
        cin>>choice;
        printf("Enter the x value: ");
        float x, y, z;
        cin>>x;
        printf("Enter the y value: ");
        cin>>y;
        printf("Enter the z value: ");
        cin>>z;
        printf("You entered: %f %f %f\n", x, y, z);
        vector<float> new_site(3);
        new_site[0] = x;
        new_site[1] = y;
        new_site[2] = z;
        TourSite ts;
        ts.site = new_site;
        ts.status = 'a';
        ts.index = -1;
        tour_array.insert(tour_array.begin()+choice,ts);
}

void constructTourArray()
{
    tour_array.clear();
    vector<vector<float> > tour_sites = gSculpture.GetTourSites();
    int CTS = gSculpture.GetCurrentTourStep();
    for (int a = 0; a < tour_sites.size(); a++)
    {
        struct TourSite ts;
        ts.index = a;
        ts.site = tour_sites[a];
        if (a <= CTS) ts.status = 'v';
        else ts.status = 'u';
        tour_array.push_back(ts);
    }
}

void handleRemoveSite()
{
    for (int a = 0; a < tour_array.size(); a++)
    {
        TourSite ts = tour_array[a];
        vector<float> site = ts.site;
        if (ts.status == 'u' || ts.status == 'a')
        {    
            printf("%d: site = (%f, %f, %f)\n", a, site[0], site[1], site[2]);
        }
    }
        int choice;
        printf("Please choose the site you wish to remove : ");
        cin>>choice;
        printf("You entered: %d\n", choice);
        tour_array[choice].status = 'd';
}

void getInput()
{
    glutKeyboardFunc(NULL);
    constructTourArray();
    int CTS = gSculpture.GetCurrentTourStep();
    printf("Press 'a' to add a site, 'd' to delete, then enter : ");
    char in;
    cin>>in;
    if (in == 'a')
    {
        printf("You entered a\n");
        handleAddSite();
        getInputHelper();
    }
    else if (in == 'd')
    {
        printf("You entered d\n");
        handleRemoveSite();
        
        getInputHelper();
    }
    else
        printf("Not one of the available inputs\n");
}

/**
 * Keyboard event handler
 */
void KeyboardCallback(unsigned char key, int xb, int yb)
{
    fprintf(stderr, "%c pressed\n", key);
//	STImage* screenshot = new STImage(gWindowSizeX, gWindowSizeY);

	/*if (gSculpture.GetFree() == 0)
	{
		if (key == 'q') exit(0);
		return;
	}*/
    float x, y, z, pitch, yaw;
    x = y = z = pitch = yaw = 0.;
    int back, stop, forward;
    back = stop = forward= 0;
    float del = 50.;
    float delA = 5.;
    switch(key)
    {
    case char(27):
    case 'q':
        exit(0);
        break;
/*
	case 's':

		//
		// Take a screenshot, and save as sculpture.png
		//
		screenshot->Read(0,0);
		screenshot->Save("sculpture.png");
		break;

    case 'w':
        //
        // Toggle wireframe
        //
        gWireframe = !gWireframe;
        if(gWireframe)
        {
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
        }
        else
        {
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        }
        break;
    }
*/
    case 'w':
        y +=del;
        break;
    case 's':
        y -=del;
        break;
    case 'a':
        x -=del;
        break;
    case 'd':
        x +=del;
        break;
    case 'e':
        z +=del;
        break;
    case 'c':
        z -=del;  
        break; 
    case 'j':
        yaw += delA;
        break;
    case 'l':
        yaw -= delA;
        break;
    case 'i':
        pitch +=delA;
        break;
    case 'k':
        pitch -=delA; 
        break;
    case 'r':
        //fprintf(stderr, "r pressed\n");
        getInput();
        break;
    case 't':
        fprintf(stderr, "you pressed t\n");
        back = 1;
        break;
    case 'y':
        stop = 1;
        fprintf(stderr, "y pressed\n");
        break;
    case 'u':
        forward = 1;
        fprintf(stderr, "u pressed\n");
        break;
    }

    if (gSculpture.GetFree())
    {
        gSculpture.AdjustCameraPosition(x,y,z);
        gSculpture.AdjustCameraAngle(pitch, yaw);
    }

    if (back) gSculpture.SetState(D_BACKWARD);
    else if (stop) gSculpture.SetState(D_STOP);
    else if (forward) gSculpture.SetState(D_FORWARD);

    gSculpture.PrintCurrentStats();
//	delete screenshot;
}

/**
 * Reshape records the new size of the window
 * for the aspect ratio computation.
 */
void ReshapeCallback(int w, int h)
{
    gWindowSizeX = w;
    gWindowSizeY = h;
}

/**
 * Mouse event handler
 */
void MouseCallback(int button, int state, int x, int y)
{
	if (button == GLUT_LEFT_BUTTON || button == GLUT_RIGHT_BUTTON)
	{
		gMouseButton = button;
	} else
	{
		gMouseButton = -1;
	}

	if (state == GLUT_UP)
	{
		gPreviousMouseX = -1;
		gPreviousMouseY = -1;
	}
}

/**
* Mouse active motion callback (when button is pressed)
/*/
void MouseMotionCallback(int x, int y)
{
	if (gPreviousMouseX >= 0 && gPreviousMouseY >= 0)
	{
		//compute delta
		float deltaX = x-gPreviousMouseX;
		float deltaY = y-gPreviousMouseY;

		gPreviousMouseX = x;
		gPreviousMouseY = y;

		float zoomSensitivity = 1.f;
		float rotateSensitivity = 0.5f;

		//orbit or zoom
		if (gMouseButton == GLUT_LEFT_BUTTON)
		{
			//gSculpture.AdjustCameraAzimuthBy(-deltaX*rotateSensitivity);
			//gSculpture.AdjustCameraElevationBy(-deltaY*rotateSensitivity);

		} else if (gMouseButton == GLUT_RIGHT_BUTTON)
		{
			STVector3 zoom(0,0,deltaX);
			//gSculpture.AdjustCameraTranslationBy(zoom * zoomSensitivity);
		}

	} else
	{
		gPreviousMouseX = x;
		gPreviousMouseY = y;
	}

}

int main(int argc, char** argv)
{
    printf("Usage:\n-c <file> control points -f free mode \n-r <radius>\n-s <file> sites\n-p first person view\n-t <file> triangles\n-v <file> vertices and edges\n");

	char c;
	int free_mode = 0;
    int first_person = 0;
	float ring_radius = -1.;
	char *cfile, *sfile, *vfile, *tfile;
	cfile = sfile = vfile = tfile = NULL;
	while ((c = getopt(argc, argv, "c:fpr:s:t:v:")) != -1)
	{
		switch (c)
		{
            case 'c':
                cfile = (char *)optarg;
                break;
			case 'f':
				fprintf(stderr,"Setting free mode\n");
				free_mode = 1;
				break;
            case 'p':
                first_person = 1;
                break;
			case 'r':
			    fprintf(stderr,"Set ring radius\n");
			    ring_radius = atof((char *)optarg);
			    if (ring_radius < 0.f)
			        fprintf(stderr, "Ring radius must be positive");
			    break;

			case 's':
			    sfile = (char *)optarg;
			    break;
            case 't':
                tfile = (char *)optarg;
                break;
			case 'v':
			    vfile = (char *)optarg;
                fprintf(stderr, "vfileis- : %s\n",vfile);
			    break;
		}
	}
	
    glutInit(&argc, argv);
    glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowPosition(20, 20);
    glutInitWindowSize(600, 600);

    window1 = glutCreateWindow("Main Window");
    glEnable(GL_DEPTH_TEST);
    gSculpture.SetCFile(cfile);

        gSculpture.SetSFile(sfile);

        gSculpture.SetVFile(vfile);
    gSculpture.SetTFile(tfile);
    Setup();
	if (free_mode)
		gSculpture.SetFree();
    if (first_person)
        gSculpture.SetFPP();
	if (ring_radius > 0.)
	    gSculpture.SetRingRadius(ring_radius);

    glutDisplayFunc(DisplayCallback);
    glutKeyboardFunc(KeyboardCallback);
    glutReshapeFunc(ReshapeCallback);
	glutMouseFunc(MouseCallback);
	glutMotionFunc(MouseMotionCallback);

	glutInitWindowPosition(700,20);
	glutInitWindowSize(600,600);
	window2 = glutCreateWindow("Secondary Window");
	glutDisplayFunc(SecondaryDisplayCallback);
	glutSetWindow(window1);

	gSculpture.SetWindow1(window1);
	gSculpture.SetWindow2(window2);
    glutMainLoop();

    // Call any cleanup code here.
    return 0;
}
